/*
 * Inode routines for 2.xBSD filesystem.
 *
 * Copyright (C) 2006-2014 Serge Vakulenko, <serge@vak.ru>
 *
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software.
 */
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include "bsdfs.h"

extern int verbose;

int fs_inode_get (fs_t *fs, fs_inode_t *inode, unsigned inum)
{
    unsigned long offset;
    int i, reserved;

    memset (inode, 0, sizeof (*inode));
    inode->fs = fs;
    inode->number = inum;

    /* Inodes are numbered starting from 1.
     * 64 bytes per inode, 16 inodes per block.
     * Skip first block. */
    if (inum == 0 || inum > (fs->isize-1) * BSDFS_INODES_PER_BLOCK)
        return 0;
    offset = (inode->number + BSDFS_INODES_PER_BLOCK - 1) *
        BSDFS_BSIZE / BSDFS_INODES_PER_BLOCK;

    if (! fs_seek (fs, offset))
        return 0;

    if (! fs_read16 (fs, &inode->mode)) /* file type and access mode */
        return 0;
    if (! fs_read16 (fs, &inode->nlink))    /* directory entries */
        return 0;
    if (! fs_read32 (fs, &inode->uid))  /* owner */
        return 0;
    if (! fs_read32 (fs, &inode->gid))  /* group */
        return 0;
    if (! fs_read32 (fs, (unsigned*) &inode->size)) /* size */
        return 0;

    for (i=0; i<NADDR; ++i) {       /* device addresses constituting file */
        if (! fs_read32 (fs, &inode->addr[i]))
            return 0;
    }
    if (! fs_read32 (fs, (unsigned*) &reserved))
        return 0;
    if (! fs_read32 (fs, (unsigned*) &inode->flags))
        return 0;
    if (! fs_read32 (fs, (unsigned*) &inode->atime))
        return 0;           /* last access time */
    if (! fs_read32 (fs, (unsigned*) &inode->mtime))
        return 0;           /* last modification time */
    if (! fs_read32 (fs, (unsigned*) &inode->ctime))
        return 0;           /* creation time */
/*if (inode->mode) { fs_inode_print (inode, stdout); printf ("---\n"); }*/
    if (verbose > 3)
        printf ("get inode %u\n", inode->number);
    return 1;
}

/*
 * Free all the disk blocks associated
 * with the specified inode structure.
 * The blocks of the file are removed
 * in reverse order. This FILO
 * algorithm will tend to maintain
 * a contiguous free list much longer
 * than FIFO.
 */
void fs_inode_truncate (fs_inode_t *inode, unsigned long size)
{
    int i, nblk;
    unsigned *blk;

    if ((inode->mode & INODE_MODE_FMT) == INODE_MODE_FCHR ||
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FBLK)
        return;

#define SINGLE  NDADDR          /* index of single indirect block */
#define DOUBLE  (SINGLE+1)      /* index of double indirect block */
#define TRIPLE  (DOUBLE+1)      /* index of triple indirect block */

    nblk = (size + BSDFS_BSIZE - 1) / BSDFS_BSIZE;
    for (i=TRIPLE; i>=0; --i) {
        blk = &inode->addr[i];
        if (*blk == 0)
            continue;

        if (i == TRIPLE) {
            if (! fs_triple_indirect_block_free (inode->fs, *blk,
                nblk - (NDADDR + BSDFS_BSIZE/4 + BSDFS_BSIZE/4*BSDFS_BSIZE/4)))
                break;
        } else if (i == DOUBLE) {
            if (! fs_double_indirect_block_free (inode->fs, *blk,
                nblk - (NDADDR + BSDFS_BSIZE/4)))
                break;
        } else if (i == SINGLE) {
            if (! fs_indirect_block_free (inode->fs, *blk, nblk - NDADDR))
                break;
        } else {
            if (i * BSDFS_BSIZE < size)
                break;
            fs_block_free (inode->fs, *blk);
        }

        *blk = 0;
    }

    inode->size = size;
    inode->dirty = 1;
}

void fs_inode_clear (fs_inode_t *inode)
{
    inode->dirty = 1;
    inode->mode = 0;
    inode->nlink = 0;
    inode->uid = 0;
    inode->size = 0;
    memset (inode->addr, 0, sizeof(inode->addr));
    inode->atime = 0;
    inode->mtime = 0;
}

int fs_inode_save (fs_inode_t *inode, int force)
{
    unsigned long offset;
    int i;

    if (! inode->fs->writable)
        return 0;
    if (! force && ! inode->dirty)
        return 1;
    if (inode->number == 0 ||
        inode->number > (inode->fs->isize-1) * BSDFS_INODES_PER_BLOCK)
        return 0;
    offset = (inode->number + BSDFS_INODES_PER_BLOCK - 1) *
        BSDFS_BSIZE / BSDFS_INODES_PER_BLOCK;

    time (&inode->atime);

    if (! fs_seek (inode->fs, offset))
        return 0;

    if (! fs_write16 (inode->fs, inode->mode))  /* file type and access mode */
        return 0;
    if (! fs_write16 (inode->fs, inode->nlink)) /* directory entries */
        return 0;
    if (! fs_write32 (inode->fs, inode->uid))   /* owner */
        return 0;
    if (! fs_write32 (inode->fs, inode->gid))   /* group */
        return 0;
    if (! fs_write32 (inode->fs, inode->size))  /* size */
        return 0;

    for (i=0; i<NADDR; ++i) {   /* device addresses constituting file */
        if (! fs_write32 (inode->fs, inode->addr[i]))
            return 0;
    }
    if (! fs_write32 (inode->fs, 0))        /* reserved */
        return 0;
    if (! fs_write32 (inode->fs, inode->flags)) /* flags */
        return 0;
    if (! fs_write32 (inode->fs, inode->atime)) /* last access time */
        return 0;
    if (! fs_write32 (inode->fs, inode->mtime)) /* last modification time */
        return 0;
    if (! fs_write32 (inode->fs, inode->ctime)) /* creation time */
        return 0;

    inode->dirty = 0;
    if (verbose > 3)
        printf ("save inode %u\n", inode->number);
    return 1;
}

void fs_inode_print (fs_inode_t *inode, FILE *out)
{
    int i;

    fprintf (out, "     I-node: %u\n", inode->number);
    fprintf (out, "       Type: %s\n",
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FDIR ? "Directory" :
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FCHR ? "Character device" :
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FBLK ? "Block device" :
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FREG ? "File" :
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FLNK ? "Symbolic link" :
        (inode->mode & INODE_MODE_FMT) == INODE_MODE_FSOCK? "Socket" :
        "Unknown");
    fprintf (out, "       Size: %lu bytes\n", inode->size);
    fprintf (out, "       Mode: %#o\n", inode->mode);

    fprintf (out, "            ");
    if (inode->mode & INODE_MODE_SUID)  fprintf (out, " SUID");
    if (inode->mode & INODE_MODE_SGID)  fprintf (out, " SGID");
    if (inode->mode & INODE_MODE_SVTX)  fprintf (out, " SVTX");
    if (inode->mode & INODE_MODE_READ)  fprintf (out, " READ");
    if (inode->mode & INODE_MODE_WRITE) fprintf (out, " WRITE");
    if (inode->mode & INODE_MODE_EXEC)  fprintf (out, " EXEC");
    fprintf (out, "\n");

    fprintf (out, "      Links: %u\n", inode->nlink);
    fprintf (out, "   Owner id: %u\n", inode->uid);

    fprintf (out, "     Blocks:");
    for (i=0; i<NADDR; ++i) {
        fprintf (out, " %u", inode->addr[i]);
    }
    fprintf (out, "\n");

    fprintf (out, "    Created: %s", ctime (&inode->ctime));
    fprintf (out, "   Modified: %s", ctime (&inode->mtime));
    fprintf (out, "Last access: %s", ctime (&inode->atime));
}

void fs_directory_scan (fs_inode_t *dir, char *dirname,
    fs_directory_scanner_t scanner, void *arg)
{
    fs_inode_t file;
    unsigned long offset;
    unsigned char name [BSDFS_BSIZE - 12];
    struct {
        unsigned int inum;
        unsigned short reclen;
        unsigned short namlen;
    } dirent;

    /* Variable record per file */
    for (offset = 0; offset < dir->size; offset += dirent.reclen) {
        if (! fs_inode_read (dir, offset, (unsigned char*) &dirent, sizeof(dirent))) {
            fprintf (stderr, "%s: read error at offset %ld\n",
                dirname[0] ? dirname : "/", offset);
            return;
        }
/*printf ("scan offset %lu: inum=%u, reclen=%u, namlen=%u\n", offset, dirent.inum, dirent.reclen, dirent.namlen);*/
        if (! fs_inode_read (dir, offset+sizeof(dirent), name, (dirent.namlen + 4) / 4 * 4)) {
            fprintf (stderr, "%s: name read error at offset %ld\n",
                dirname[0] ? dirname : "/", offset);
            return;
        }
/*printf ("scan offset %lu: name='%s'\n", offset, name);*/

        if (dirent.inum == 0 || (name[0]=='.' && name[1]==0) ||
            (name[0]=='.' && name[1]=='.' && name[2]==0))
            continue;

        if (! fs_inode_get (dir->fs, &file, dirent.inum)) {
            fprintf (stderr, "cannot scan inode %d\n", dirent.inum);
            continue;
        }
        scanner (dir, &file, dirname, (char*) name, arg);
    }
}

/*
 * Return the physical block number on a device given the
 * inode and the logical block number in a file.
 */
static unsigned map_block (fs_inode_t *inode, unsigned lbn)
{
    unsigned block [BSDFS_BSIZE / 4];
    unsigned int nb, i, j, sh;

    /*
     * Blocks 0..NADDR-4 are direct blocks.
     */
    if (lbn < NADDR-3) {
        /* small file algorithm */
        return inode->addr [lbn];
    }

    /*
     * Addresses NADDR-3, NADDR-2, and NADDR-1
     * have single, double, triple indirect blocks.
     * The first step is to determine
     * how many levels of indirection.
     */
    sh = 0;
    nb = 1;
    lbn -= NADDR-3;
    for (j=3; ; j--) {
        if (j == 0)
            return 0;
        sh += NSHIFT;
        nb <<= NSHIFT;
        if (lbn < nb)
            break;
        lbn -= nb;
    }

    /*
     * Fetch the first indirect block.
     */
    nb = inode->addr [NADDR-j];
    if (nb == 0)
        return 0;

    /*
     * Fetch through the indirect blocks.
     */
    for(; j <= 3; j++) {
        if (! fs_read_block (inode->fs, nb, (unsigned char*) block))
            return 0;

        sh -= NSHIFT;
        i = (lbn >> sh) & NMASK;
        nb = block [i];
        if (nb == 0)
            return 0;
    }
    return nb;
}

/*
 * Bmap defines the structure of file system storage
 * by returning the physical block number on a device given the
 * inode and the logical block number in a file.
 */
static unsigned map_block_write (fs_inode_t *inode, unsigned lbn)
{
    unsigned block [BSDFS_BSIZE / 4];
    unsigned int nb, newb, sh, i, j;

    /*
     * Blocks 0..NADDR-3 are direct blocks.
     */
    if (lbn < NADDR-3) {
        /* small file algorithm */
        nb = inode->addr [lbn];
        if (nb != 0) {
            if (verbose)
                printf ("map logical block %d to physical %d\n", lbn, nb);
            return nb;
        }

        /* allocate new block */
        if (! fs_block_alloc (inode->fs, &nb))
            return 0;
        inode->addr[lbn] = nb;
        inode->dirty = 1;
        return nb;
    }

    /*
     * Addresses NADDR-3, NADDR-2, and NADDR-1
     * have single, double, triple indirect blocks.
     * The first step is to determine
     * how many levels of indirection.
     */
    sh = 0;
    nb = 1;
    lbn -= NADDR-3;
    for (j=3; ; j--) {
        if (j == 0)
            return 0;
        sh += NSHIFT;
        nb <<= NSHIFT;
        if (lbn < nb)
            break;
        lbn -= nb;
    }

    /*
     * Fetch the first indirect block.
     */
    nb = inode->addr [NADDR-j];
    if (nb == 0) {
        if (! fs_block_alloc (inode->fs, &nb))
            return 0;
        if (verbose)
            printf ("inode %d: allocate new block %d\n", inode->number, nb);
        memset (block, 0, BSDFS_BSIZE);
        if (! fs_write_block (inode->fs, nb, (unsigned char*) block))
            return 0;
        inode->addr [NADDR-j] = nb;
        inode->dirty = 1;
    }

    /*
     * Fetch through the indirect blocks
     */
    for(; j <= 3; j++) {
        if (! fs_read_block (inode->fs, nb, (unsigned char*) block))
            return 0;

        sh -= NSHIFT;
        i = (lbn >> sh) & NMASK;
        if (block [i] != 0)
            nb = block [i];
        else {
            /* Allocate new block. */
            if (! fs_block_alloc (inode->fs, &newb))
                return 0;
            if (verbose)
                printf ("inode %d: allocate new block %d\n", inode->number, newb);
            block[i] = newb;
            if (! fs_write_block (inode->fs, nb, (unsigned char*) block))
                return 0;
            memset (block, 0, BSDFS_BSIZE);
            if (! fs_write_block (inode->fs, newb, (unsigned char*) block))
                return 0;
            nb = newb;
        }
    }
    return nb;
}

int fs_inode_read (fs_inode_t *inode, unsigned long offset,
    unsigned char *data, unsigned long bytes)
{
    unsigned char block [BSDFS_BSIZE];
    unsigned long n;
    unsigned int bn, inblock_offset;

    if (bytes + offset > inode->size)
        return 0;
    while (bytes != 0) {
        inblock_offset = offset % BSDFS_BSIZE;
        n = BSDFS_BSIZE - inblock_offset;
        if (n > bytes)
            n = bytes;

        bn = map_block (inode, offset / BSDFS_BSIZE);
        if (bn == 0)
            return 0;

        if (! fs_read_block (inode->fs, bn, block))
            return 0;
        memcpy (data, block + inblock_offset, n);
        data += n;
        offset += n;
        bytes -= n;
    }
    return 1;
}

int fs_inode_write (fs_inode_t *inode, unsigned long offset,
    unsigned char *data, unsigned long bytes)
{
    unsigned char block [BSDFS_BSIZE];
    unsigned long n;
    unsigned int bn, inblock_offset;

    time (&inode->mtime);
    while (bytes != 0) {
        inblock_offset = offset % BSDFS_BSIZE;
        n = BSDFS_BSIZE - inblock_offset;
        if (n > bytes)
            n = bytes;

        bn = map_block_write (inode, offset / BSDFS_BSIZE);
        if (bn == 0)
            return 0;
        if (inode->size < offset + n) {
            /* Increase file size. */
            inode->size = offset + n;
            inode->dirty = 1;
        }
        if (verbose)
            printf ("inode %d offset %ld: write %ld bytes to block %d\n",
                inode->number, offset, n, bn);

        if (n == BSDFS_BSIZE) {
            if (! fs_write_block (inode->fs, bn, data))
                return 0;
        } else {
            if (! fs_read_block (inode->fs, bn, block))
                return 0;
            memcpy (block + inblock_offset, data, n);
            if (! fs_write_block (inode->fs, bn, block))
                return 0;
        }
        data += n;
        offset += n;
        bytes -= n;
    }
    return 1;
}

/*
 * Convert from dirent to raw data.
 */
void fs_dirent_pack (unsigned char *data, fs_dirent_t *dirent)
{
    int i;

    *data++ = dirent->ino;
    *data++ = dirent->ino >> 8;
    *data++ = dirent->ino >> 16;
    *data++ = dirent->ino >> 24;
    *data++ = dirent->reclen;
    *data++ = dirent->reclen >> 8;
    *data++ = dirent->namlen;
    *data++ = dirent->namlen >> 8;
    for (i=0; dirent->name[i]; ++i)
        *data++ = dirent->name[i];
    for (; i & 3; ++i)
        *data++ = 0;
}

/*
 * Read dirent from raw data.
 */
void fs_dirent_unpack (fs_dirent_t *dirent, unsigned char *data)
{
    dirent->ino = *data++;
    dirent->ino |= *data++ << 8;
    dirent->ino |= *data++ << 16;
    dirent->ino |= *data++ << 24;
    dirent->reclen = *data++;
    dirent->reclen |= *data++ << 8;
    dirent->namlen = *data++;
    dirent->namlen |= *data++ << 8;
    memset (dirent->name, 0, sizeof (dirent->name));
    memcpy (dirent->name, data, dirent->namlen);
}

/*
 * Convert a pathname into a pointer to
 * an inode. Note that the inode is locked.
 *
 * op = 0 if name is saught
 *  1 if name is to be created, mode is given
 *  2 if name is to be deleted
 *  3 if name is to be linked, mode contains inode number
 *
 * Return 0 on any error.
 * Return 1 when the inode was found.
 * Return 2 when the inode was created/deleted/linked.
 */
int fs_inode_by_name (fs_t *fs, fs_inode_t *inode, const char *name,
    fs_op_t op, int mode)
{
    fs_inode_t dir;
    int c, namlen, reclen;
    const char *namptr;
    unsigned long offset, last_offset;
    struct {
        unsigned int inum;
        unsigned short reclen;
        unsigned short namlen;
    } dirent;

    /* Start from root. */
    if (! fs_inode_get (fs, &dir, BSDFS_ROOT_INODE)) {
        fprintf (stderr, "inode_open(): cannot get root\n");
        return 0;
    }
    c = *name++;
    while (c == '/')
        c = *name++;
    if (! c && op != INODE_OP_LOOKUP) {
        /* Cannot write or delete root directory. */
        return 0;
    }
cloop:
    /* Here inode contains pointer
     * to last component matched. */
    if (! c) {
        *inode = dir;
        return 1;
    }

    /* If there is another component,
     * inode must be a directory. */
    if ((dir.mode & INODE_MODE_FMT) != INODE_MODE_FDIR) {
        return 0;
    }

    /* Gather up dir name into buffer. */
    namptr = name - 1;
    while (c && c != '/') {
        c = *name++;
    }
    namlen = name - namptr - 1;
    while (c == '/')
        c = *name++;

    /* Search a directory, variable record per file */
    if (verbose > 2)
        printf ("scan for '%.*s', %d bytes\n", namlen, namptr, namlen);
    last_offset = 0;
    for (offset = 0; offset < dir.size; last_offset = offset, offset += dirent.reclen) {
        unsigned char fname [BSDFS_BSIZE - 12];

        if (! fs_inode_read (&dir, offset, (unsigned char*) &dirent, sizeof(dirent))) {
            fprintf (stderr, "inode %d: read error at offset %ld\n",
                dir.number, offset);
            return 0;
        }
        if (verbose > 2)
            printf ("scan offset %lu: inum=%u, reclen=%u, namlen=%u\n", offset, dirent.inum, dirent.reclen, dirent.namlen);
        if (dirent.inum == 0 || dirent.namlen != namlen)
            continue;
        if (! fs_inode_read (&dir, offset+sizeof(dirent), fname, namlen)) {
            fprintf (stderr, "inode %d: name read error at offset %ld\n",
                dir.number, offset);
            return 0;
        }
        if (verbose > 2)
            printf ("scan offset %lu: name='%.*s'\n", offset, namlen, fname);
        if (strncmp (namptr, (char*) fname, namlen) == 0) {
            /* Here a component matched in a directory.
             * If there is more pathname, go back to
             * cloop, otherwise return. */
            if (op == INODE_OP_DELETE && ! c) {
                goto delete_file;
            }
            if (! fs_inode_get (fs, &dir, dirent.inum)) {
                fprintf (stderr, "inode_open(): cannot get inode %d\n", dirent.inum);
                return 0;
            }
            goto cloop;
        }
    }
    /* If at the end of the directory, the search failed.
     * Report what is appropriate as per flag. */
    if (op == INODE_OP_CREATE && ! c)
        goto create_file;
    if (op == INODE_OP_LINK && ! c)
        goto create_link;
    return 0;

    /*
     * Make a new file, and return it's inode.
     */
create_file:
    if (! fs_inode_alloc (fs, inode)) {
        fprintf (stderr, "%s: cannot allocate inode\n", namptr);
        return 0;
    }
    inode->dirty = 1;
    inode->mode = mode & (07777 | INODE_MODE_FMT);
    if ((inode->mode & INODE_MODE_FMT) == 0)
        inode->mode |= INODE_MODE_FREG;
    inode->nlink = 1;
    inode->uid = 0;
    inode->flags = 0;
    time (&inode->ctime);
    if ((inode->mode & INODE_MODE_FMT) == INODE_MODE_FDIR) {
        /* Make link '.' */
        struct {
            unsigned int inum;
            unsigned short reclen;
            unsigned short namlen;
            char name [4];
        } dotent;
        dotent.inum = inode->number;
        dotent.reclen = BSDFS_BSIZE;
        dotent.namlen = 1;
        memcpy (dotent.name, ".\0\0\0", 4);
        if (! fs_inode_write (inode, 0, (unsigned char*) &dotent, sizeof(dotent))) {
            fprintf (stderr, "inode %d: write error at offset %ld\n",
                inode->number, 0L);
            return 0;
        }
        /* Increase file size. */
        inode->size = BSDFS_BSIZE;
        ++inode->nlink;
    }
    if (! fs_inode_save (inode, 0)) {
        fprintf (stderr, "%s: cannot save file inode\n", namptr);
        return 0;
    }

    /* Write a directory entry. */
    if (verbose > 2)
        printf ("*** create file '%.*s', inode %d\n", namlen, namptr, inode->number);
    reclen = dirent.reclen - 8 - (dirent.namlen + 4) / 4 * 4;
    c = 8 + (namlen + 4) / 4 * 4;
    if (reclen >= c) {
        /* Enough space */
        dirent.reclen -= reclen;
        if (verbose > 2)
            printf ("*** previous entry %u-%u-%u at offset %lu\n",
                dirent.inum, dirent.reclen, dirent.namlen, last_offset);
        if (! fs_inode_write (&dir, last_offset, (unsigned char*) &dirent, sizeof(dirent))) {
            fprintf (stderr, "inode %d: write error at offset %ld\n",
                dir.number, last_offset);
            return 0;
        }
    } else {
        /* No space, extend directory. */
        if (verbose > 2)
            printf ("*** extend dir, previous entry %u-%u-%u at offset %lu\n",
                dirent.inum, dirent.reclen, dirent.namlen, last_offset);
        reclen = BSDFS_BSIZE;
    }
    offset = last_offset + dirent.reclen;
    dirent.inum = inode->number;
    dirent.reclen = reclen;
    dirent.namlen = namlen;
    if (verbose > 2)
        printf ("*** new entry %u-%u-%u at offset %lu\n", dirent.inum, dirent.reclen, dirent.namlen, offset);
    if (! fs_inode_write (&dir, offset, (unsigned char*) &dirent, sizeof(dirent))) {
        fprintf (stderr, "inode %d: write error at offset %ld\n",
            dir.number, offset);
        return 0;
    }
    if (verbose > 2)
        printf ("*** name '%.*s' at offset %lu\n", namlen, namptr, offset+sizeof(dirent));
    if (! fs_inode_write (&dir, offset+sizeof(dirent), (unsigned char*) namptr, namlen) ||
        ! fs_inode_write (&dir, offset+sizeof(dirent)+namlen, (unsigned char*) "", 1)) {
        fprintf (stderr, "inode %d: write error at offset %ld\n",
            dir.number, offset+sizeof(dirent));
        return 0;
    }
    /* Align directory size. */
    dir.size = (dir.size + BSDFS_BSIZE - 1) / BSDFS_BSIZE * BSDFS_BSIZE;
    if (! fs_inode_save (&dir, 0)) {
        fprintf (stderr, "%s: cannot save directory inode\n", namptr);
        return 0;
    }
    return 2;

    /*
     * Delete file. Return inode of deleted file.
     */
delete_file:
    if (verbose > 2)
        printf ("*** delete inode %d\n", dirent.inum);
    if (! fs_inode_get (fs, inode, dirent.inum)) {
        fprintf (stderr, "%s: cannot get inode %d\n", namptr, dirent.inum);
        return 0;
    }
    inode->dirty = 1;
    inode->nlink--;
    if (inode->nlink <= 0) {
        fs_inode_truncate (inode, 0);
        fs_inode_clear (inode);
        if (inode->fs->ninode < NICINOD) {
            inode->fs->inode [inode->fs->ninode++] = dirent.inum;
            inode->fs->dirty = 1;
        }
    }
    /* Extend previous entry to cover the empty space. */
    reclen = dirent.reclen;
    if (! fs_inode_read (&dir, last_offset, (unsigned char*) &dirent, sizeof(dirent))) {
        fprintf (stderr, "inode %d: read error at offset %ld\n",
            dir.number, last_offset);
        return 0;
    }
    dirent.reclen += reclen;
    if (! fs_inode_write (&dir, last_offset, (unsigned char*) &dirent, sizeof(dirent))) {
        fprintf (stderr, "inode %d: write error at offset %ld\n",
            dir.number, last_offset);
        return 0;
    }
    if (! fs_inode_save (&dir, 0)) {
        fprintf (stderr, "%s: cannot save directory inode\n", namptr);
        return 0;
    }
    return 2;

    /*
     * Make a link. Return a directory inode.
     */
create_link:
    if (verbose > 2)
        printf ("*** link inode %d to '%.*s', directory %d\n", mode, namlen, namptr, dir.number);
    reclen = dirent.reclen - 8 - (dirent.namlen + 4) / 4 * 4;
    c = 8 + (namlen + 4) / 4 * 4;
    if (reclen >= c) {
        /* Enough space */
        dirent.reclen -= reclen;
        if (verbose > 2)
            printf ("*** previous entry %u-%u-%u at offset %lu\n",
                dirent.inum, dirent.reclen, dirent.namlen, last_offset);
        if (! fs_inode_write (&dir, last_offset, (unsigned char*) &dirent, sizeof(dirent))) {
            fprintf (stderr, "inode %d: write error at offset %ld\n",
                dir.number, last_offset);
            return 0;
        }
    } else {
        /* No space, extend directory. */
        if (verbose > 2)
            printf ("*** extend dir, previous entry %u-%u-%u at offset %lu\n",
                dirent.inum, dirent.reclen, dirent.namlen, last_offset);
        reclen = BSDFS_BSIZE;
    }
    offset = last_offset + dirent.reclen;
    dirent.inum = mode;
    dirent.reclen = reclen;
    dirent.namlen = namlen;
    if (verbose > 2)
        printf ("*** new entry %u-%u-%u at offset %lu\n", dirent.inum, dirent.reclen, dirent.namlen, offset);
    if (! fs_inode_write (&dir, offset, (unsigned char*) &dirent, sizeof(dirent))) {
        fprintf (stderr, "inode %d: write error at offset %ld\n",
            dir.number, offset);
        return 0;
    }
    if (verbose > 2)
        printf ("*** name '%.*s' at offset %lu\n", namlen, namptr, offset+sizeof(dirent));
    if (! fs_inode_write (&dir, offset+sizeof(dirent), (unsigned char*) namptr, namlen) ||
        ! fs_inode_write (&dir, offset+sizeof(dirent)+namlen, (unsigned char*) "", 1)) {
        fprintf (stderr, "inode %d: write error at offset %ld\n",
            dir.number, offset+sizeof(dirent));
        return 0;
    }
    if (dir.size % BSDFS_BSIZE) {
        /* Align directory size. */
        dir.size = (dir.size + BSDFS_BSIZE - 1) / BSDFS_BSIZE * BSDFS_BSIZE;
        dir.dirty = 1;
    }
    if (! fs_inode_save (&dir, 0)) {
        fprintf (stderr, "%s: cannot save directory inode\n", namptr);
        return 0;
    }
    *inode = dir;
    return 2;
}

/*
 * Allocate an unused I node on the specified device.
 * Used with file creation.  The algorithm keeps up to
 * NICINOD spare I nodes in the super block.
 * When this runs out, a linear search through the
 * I list is instituted to pick up NICINOD more.
 */
int fs_inode_alloc (fs_t *fs, fs_inode_t *inode)
{
    int ino;

    for (;;) {
        if (fs->ninode <= 0) {
            /* Build a list of free inodes. */
            if (! inode_build_list (fs)) {
                fprintf (stderr, "inode_alloc: cannot build inode list\n");
                return 0;
            }
            if (fs->ninode <= 0) {
                fprintf (stderr, "inode_alloc: out of in-core inodes\n");
                return 0;
            }
        }
        ino = fs->inode[--fs->ninode];
        fs->dirty = 1;
        fs->tinode--;
        if (! fs_inode_get (fs, inode, ino)) {
            fprintf (stderr, "inode_alloc: cannot get inode %d\n", ino);
            return 0;
        }
        if (inode->mode == 0) {
            fs_inode_clear (inode);
            return 1;
        }
    }
}

/*
 * Find inode by name.
 * Return 0 on any error.
 * Return 1 when the inode was found.
 */
int fs_inode_lookup (fs_t *fs, fs_inode_t *inode, const char *name)
{
    return fs_inode_by_name (fs, inode, name, INODE_OP_LOOKUP, 0);
}

/*
 * Create inode by name.
 * Return 0 on any error.
 * Return 1 when the inode was found.
 * Return 2 when the inode was created.
 */
int fs_inode_create (fs_t *fs, fs_inode_t *inode, const char *name, int mode)
{
    return fs_inode_by_name (fs, inode, name, INODE_OP_CREATE, mode);
}

/*
 * Delete inode by name.
 * Return 0 on any error.
 * Return 2 when the inode was created.
 */
int fs_inode_delete (fs_t *fs, fs_inode_t *inode, const char *name)
{
    return fs_inode_by_name (fs, inode, name, INODE_OP_DELETE, 0);
}

/*
 * Create a new link for the inode.
 * Return 0 on any error.
 * Return 2 when the inode was linked.
 */
int fs_inode_link (fs_t *fs, fs_inode_t *inode, const char *name, int inum)
{
    return fs_inode_by_name (fs, inode, name, INODE_OP_LINK, inum);
}
